// src/parsers/scriptUniversalParser.ts
import type { ParseUniversalResult } from '../universalParser.ts';
import type { UniversalFlowNode, UniversalFlowEdge, SourceFormat, NodeSemanticType, UniversalFlowNodeData } from '@/models/universalFlow.ts';
import { v4 as uuidv4 } from 'uuid';

// Import necessary SWC types
import type {
    Module, Expression, ExportDefaultExpression, ObjectExpression, ArrayExpression,
    Property, KeyValueProperty, Identifier, StringLiteral, NumericLiteral, BooleanLiteral, NullLiteral, SpreadElement,
    PropertyName, VariableDeclaration, VariableDeclarator,
    CallExpression, TemplateLiteral, MemberExpression, Span,
    // --- Import additional expression types ---
    BinaryExpression, UnaryExpression, ConditionalExpression, NewExpression 
} from '@swc/wasm-web';

// Placeholder for future imports from @swc/wasm-web if needed for types
// import type { Program } from '@swc/wasm-web';

// Import the SWC service
import { swcService } from '@/services/swcService.ts';

// --- AST to Universal Flow Conversion --- 

interface ConversionContext {
    nodes: UniversalFlowNode[];
    edges: UniversalFlowEdge[];
    format: SourceFormat;
    level: number;
    path: string;
    order: number;
    rawCode: string;
}

// Helper function to extract code snippet from raw source using SWC span
function getExpressionString(rawCode: string, span: Span): string {
    // NOTE: SWC span provides byte offsets. String.slice uses UTF-16 code unit indices.
    // This simple slice might be incorrect for source code containing multi-byte characters
    // within the expression span. A more robust solution might involve byte-level processing
    // or mapping byte offsets to character indices if needed later.
    // We add 1 to start because span.start is often the beginning of the token,
    // while span.end points *after* the last character. Adjust if needed based on testing.
    // SWC's span context seems to be 0-based for start and exclusive for end.
    try {
        // Directly use span.start and span.end as indices for slice
        return rawCode.slice(span.start, span.end);
    } catch (e) {
        console.error(`[Script Parser] Error slicing raw code with span ${span.start}-${span.end}`, e);
        return "[Error extracting expression]";
    }
}

function transformSwcNode(swcNode: Expression | SpreadElement | null, parentId: string, context: ConversionContext): string | null {
    if (!swcNode || swcNode.type === 'SpreadElement') { // Ignore spread elements for now
        console.warn("[Script Parser] Unsupported SpreadElement encountered.");
        return null;
    }

    const nodeId = uuidv4();
    const nodeData: Partial<UniversalFlowNodeData> = {
        name: '' // Will be set based on context (key or index)
    };
    let nodeSemanticType: NodeSemanticType | null = null;
    let nodeValue: any = undefined;
    const childrenIds: string[] = [];
    let nodeMetadata: Record<string, any> = {}; // Initialize metadata object

    // --- Determine Node Type and Value from SWC Node --- 
    switch (swcNode.type) {
        case 'ObjectExpression':
            nodeSemanticType = 'object';
            let propOrder = 0;
            for (const prop of swcNode.properties) {
                if (prop.type === 'KeyValueProperty') {
                    const key = getKeyName(prop.key);
                    if (key === null) continue; // Skip unsupported keys
                    
                    // Recursively transform value, PASS rawCode
                    const childContext: ConversionContext = { ...context, level: context.level + 1, path: `${context.path}.${key}`, order: propOrder++ /* rawCode is implicitly copied by spread */ };
                    const childId = transformSwcNode(prop.value, nodeId, childContext);
                    
                    if (childId) {
                         // Assign the key name to the child node AFTER it's created
                         const childNode = context.nodes.find(n => n.id === childId);
                         if (childNode && childNode.data) {
                             childNode.data.name = key;
                         }
                         childrenIds.push(childId);
                    }
                } else {
                    console.warn(`[Script Parser] Unsupported property type: ${prop.type}`);
                    // Handle SpreadElement or other property types if needed later
                }
            }
            break;
        case 'ArrayExpression':
            nodeSemanticType = 'array';
            let elementOrder = 0;
            for (const element of swcNode.elements) {
                 if (element) { // element can be null for empty slots like [,,]
                     // Pass null as name for array elements, index is implicit via order
                     // PASS rawCode in context
                     const childContext: ConversionContext = { ...context, level: context.level + 1, path: `${context.path}[${elementOrder}]`, order: elementOrder++ /* rawCode is implicitly copied by spread */ };
                     const childId = transformSwcNode(element.expression, nodeId, childContext);
                     if (childId) {
                         // Array elements don't have a 'name' per se, index is used via order
                         childrenIds.push(childId);
                     }
                 }
            }
            break;
        case 'StringLiteral':
            nodeSemanticType = 'value';
            nodeValue = swcNode.value;
            break;
        case 'NumericLiteral':
            nodeSemanticType = 'value';
            nodeValue = swcNode.value;
            break;
        case 'BooleanLiteral':
            nodeSemanticType = 'value';
            nodeValue = swcNode.value;
            break;
        case 'NullLiteral':
            nodeSemanticType = 'value';
            nodeValue = null;
            break;
        case 'Identifier': 
            if (swcNode.value === 'undefined') {
                nodeSemanticType = 'value';
                nodeValue = undefined; 
            } else {
                nodeSemanticType = 'variableReference';
                nodeMetadata.variableName = swcNode.value;
                console.warn(`[Script Parser] Encountered variable reference: ${swcNode.value}`);
            }
            break;
        case 'CallExpression':
            nodeSemanticType = 'functionCall';
            nodeMetadata.isDynamicExpression = true; // Mark as dynamic
            nodeMetadata.expressionString = getExpressionString(context.rawCode, swcNode.span); // Get expression string
            if (swcNode.callee.type === 'Identifier') {
                 nodeMetadata.functionName = swcNode.callee.value;
            } else {
                 nodeMetadata.functionName = '[complex callee]';
            }
            console.warn(`[Script Parser] Encountered function call.`);
            break;
        case 'TemplateLiteral': 
             nodeSemanticType = 'templateLiteral';
             nodeMetadata.isDynamicExpression = true; // Mark as dynamic
             nodeMetadata.expressionString = getExpressionString(context.rawCode, swcNode.span); // Get expression string
             console.warn(`[Script Parser] Encountered template literal.`);
             break;
        case 'MemberExpression': 
             nodeSemanticType = 'memberExpression';
             nodeMetadata.isDynamicExpression = true; // Mark as dynamic
             nodeMetadata.expressionString = getExpressionString(context.rawCode, swcNode.span); // Get expression string
             nodeMetadata.expressionType = swcNode.type;
             console.warn(`[Script Parser] Encountered dynamic expression type: ${swcNode.type}`);
             break;
        case 'BinaryExpression':
             nodeSemanticType = 'binaryExpression';
             nodeMetadata.isDynamicExpression = true; // Mark as dynamic
             nodeMetadata.expressionString = getExpressionString(context.rawCode, swcNode.span); // Get expression string
             nodeMetadata.operator = swcNode.operator;
             console.warn(`[Script Parser] Encountered dynamic expression type: ${swcNode.type}`);
             break;
        case 'UnaryExpression':
             nodeSemanticType = 'unaryExpression';
             nodeMetadata.isDynamicExpression = true; // Mark as dynamic
             nodeMetadata.expressionString = getExpressionString(context.rawCode, swcNode.span); // Get expression string
             nodeMetadata.operator = swcNode.operator;
             console.warn(`[Script Parser] Encountered dynamic expression type: ${swcNode.type}`);
             break;
        case 'ConditionalExpression':
             nodeSemanticType = 'conditionalExpression';
             nodeMetadata.isDynamicExpression = true; // Mark as dynamic
             nodeMetadata.expressionString = getExpressionString(context.rawCode, swcNode.span); // Get expression string
             console.warn(`[Script Parser] Encountered dynamic expression type: ${swcNode.type}`);
             break;
        case 'NewExpression':
             nodeSemanticType = 'newExpression';
             nodeMetadata.isDynamicExpression = true; // Mark as dynamic
             nodeMetadata.expressionString = getExpressionString(context.rawCode, swcNode.span); // Get expression string
             if (swcNode.callee.type === 'Identifier') {
                 nodeMetadata.constructorName = swcNode.callee.value;
             }
             console.warn(`[Script Parser] Encountered dynamic expression type: ${swcNode.type}`);
             break;
        // --- Cases for other unsupported types we want to mark as dynamic --- 
        case 'ArrowFunctionExpression':
        case 'FunctionExpression':
            console.warn(`[Script Parser] Ignoring function definition: ${swcNode.type}`);
            return null;
        default:
            // Check if it's a literal we haven't handled yet
            if ('value' in swcNode && (typeof swcNode.value === 'string' || typeof swcNode.value === 'number' || typeof swcNode.value === 'boolean')) {
                 nodeSemanticType = 'value';
                 nodeValue = swcNode.value;
            } else {
                 console.warn(`[Script Parser] Unknown or unhandled SWC node type: ${swcNode.type}`);
                 return null;
            }
    }

    // Ensure semantic type was set if we didn't return null earlier
    if (nodeSemanticType === null) {
        console.error(`[Script Parser] Node semantic type is null after switch, should not happen. Node:`, swcNode);
        return null;
    }

    // --- Create Universal Flow Node --- 
    const universalNode: UniversalFlowNode = {
        id: nodeId,
        type: 'universal', 
        position: { x: 0, y: 0 }, 
        data: {
            sourceElement: swcNode, 
            name: '', 
            content: undefined, 
            attributes: {}, 
            path: context.path,
            format: context.format,
            nodeSemanticType: nodeSemanticType, // Use determined type
            childrenIds: childrenIds.length > 0 ? childrenIds : undefined,
            value: nodeValue, // Will be undefined for dynamic types
            order: context.order,
            level: context.level,
            metadata: nodeMetadata // Include gathered metadata
        }
    };

    context.nodes.push(universalNode);

    // --- Create Edge from Parent --- 
    context.edges.push({
        id: `e-${parentId}-${nodeId}`,
        source: parentId,
        target: nodeId,
        type: 'smoothstep', 
        data: { relationshipType: 'parent-child' }
    });

    return nodeId;
}

// Helper to get key name from SWC property key
function getKeyName(key: PropertyName): string | null { 
    if (key.type === 'Identifier') {
        return key.value;
    } else if (key.type === 'StringLiteral') {
        return key.value;
    } else if (key.type === 'NumericLiteral') {
        return String(key.value); 
    } else if (key.type === 'BigIntLiteral') { 
        return key.value.toString() + 'n'; 
    } else if (key.type === 'Computed') { 
        console.warn(`[Script Parser] Unsupported computed property key.`);
        return null;
    // Note: SWC's PropertyName type doesn't seem to include PrivateName directly 
    // according to some definitions, but we handle it defensively if needed elsewhere.
    } else {
        // This handles any unexpected or future PropertyName types
        // It also satisfies TypeScript's exhaustion check if all known types are covered above.
        const _exhaustiveCheck: never = key; 
        console.warn(`[Script Parser] Unknown or unhandled key type: ${(_exhaustiveCheck as any)?.type}`);
        return null;
    }
}

/**
 * Finds the root ObjectExpression from an `export default` and converts the AST.
 * Handles both direct object export and exporting a variable initialized with an object.
 */
function convertSwcAstToUniversal(ast: Module, format: SourceFormat, rawCode: string): Omit<ParseUniversalResult, 'rawDocument'> & { error?: string } {
    const nodes: UniversalFlowNode[] = [];
    const edges: UniversalFlowEdge[] = [];
    let rootId: string | null = null;
    let success = false;
    let error: string | undefined = undefined;
    let rootExpression: ObjectExpression | null = null; // Store the target object expression

    const exportDefault = ast.body.find((item): item is ExportDefaultExpression => item.type === 'ExportDefaultExpression');

    if (exportDefault) {
        const expression = exportDefault.expression;

        if (expression.type === 'ObjectExpression') {
            // Case 1: export default { ... }
            rootExpression = expression;
        } else if (expression.type === 'Identifier') {
            // Case 2: export default variable;
            const exportName = expression.value;
            console.log(`[Script Parser] Found export default identifier: ${exportName}`);

            for (const item of ast.body) {
                // --- Add Type Guard for VariableDeclaration --- 
                if (item.type === 'VariableDeclaration') {
                    // Check kind *after* confirming type
                    if (item.kind === 'const' || item.kind === 'let' || item.kind === 'var') {
                        // Access declarations *after* confirming type
                        for (const declaration of item.declarations) {
                            if (declaration.id.type === 'Identifier' && declaration.id.value === exportName) {
                                if (declaration.init && declaration.init.type === 'ObjectExpression') {
                                    rootExpression = declaration.init;
                                    console.log(`[Script Parser] Found variable declaration for ${exportName} with ObjectExpression.`);
                                    break; 
                                } else {
                                    error = `Variable '${exportName}' is exported but not initialized with an object literal.`;
                                    // We found the var but it's not an object, stop searching for this var
                                    break; 
                                }
                            }
                        }
                    }
                }
                if (rootExpression || error) break; // Stop searching body if found or specific error occurred
            }
            if (!rootExpression && !error) {
                 error = `Could not find variable declaration for exported identifier '${exportName}'.`;
            }
        } else {
            // Case 3: Unsupported export type
            error = `Unsupported export type: ${expression.type}. Only object literals or variables initialized with object literals are supported.`;
        }
    } else {
        error = 'Could not find default export (`export default ...`).';
    }

    // --- If a root ObjectExpression was found, proceed with conversion --- 
    if (rootExpression) {
        rootId = uuidv4();
        const rootNode: UniversalFlowNode = {
            id: rootId,
            type: 'universal',
            position: { x: 0, y: 0 },
            data: {
                sourceElement: rootExpression, // Use the found object expression
                name: '[root]', 
                path: '',
                format: format,
                nodeSemanticType: 'root',
                level: 0,
                attributes: {},
                metadata: {}
            }
        };
        nodes.push(rootNode);

        const context: ConversionContext = { nodes, edges, format, level: 0, path: '', order: 0, rawCode };
        transformSwcNode(rootExpression, rootId, context);
        
        const rootNodeRef = nodes.find(n => n.id === rootId);
        if (rootNodeRef && rootNodeRef.data) {
            rootNodeRef.data.childrenIds = edges.filter(e => e.source === rootId).map(e => e.target);
        }
        success = true;
    } else {
        // If no rootExpression found and no specific error was set, use a general one
        if (!error) {
             error = 'Could not find a suitable object literal to visualize from the default export.';
        }
        success = false;
    }

    return { success, nodes, edges, rootId, error };
}

/**
 * Parses JavaScript or TypeScript code using SWC to extract a configuration object.
 * Currently targets `export default { ... }` structures.
 * 
 * @param content The script content (JS/TS code).
 * @param format The detected source format ('javascript' or 'typescript').
 * @returns A ParseUniversalResult containing the parsed nodes and edges or an error.
 */
export async function parseScriptToUniversal(
    content: string,
    format: 'javascript' | 'typescript'
): Promise<ParseUniversalResult> {
    console.log(`[Script Parser] Attempting to parse ${format} content...`);

    let ast: Module | undefined = undefined;
    try {
        const syntax = format === 'typescript' ? 'typescript' : 'ecmascript';
        const tsx = false;

        // Use the SWC service to parse the code
        ast = await swcService.parseCode(content, {
            syntax: syntax,
            tsx: tsx,
            target: 'es2022',
            comments: false, // Keep comments off for now, affects span calculation
        });

        // --- Convert AST, PASSING raw code 'content' ---
        const conversionResult = convertSwcAstToUniversal(ast, format, content);

        if (conversionResult.success) {
            console.log("[Script Parser] AST converted successfully.")
            return {
                success: true,
                nodes: conversionResult.nodes,
                edges: conversionResult.edges,
                rootId: conversionResult.rootId,
                rawDocument: ast,
                error: undefined
            };
        } else {
             console.error("[Script Parser] Failed to convert AST to Universal Flow.");
             return {
                 success: false,
                 nodes: [],
                 edges: [],
                 rootId: null,
                 rawDocument: ast,
                 error: conversionResult.error || 'Failed to convert AST.'
             };
        }

    } catch (error: any) {
        console.error('[Script Parser] Error during SWC parsing or conversion:', error);
        let errorMessage = 'Failed to parse script content.';
        if (error instanceof Error) {
            // If the error came from the service check, use its message
            if (error.message === 'SWC is not ready.') {
                errorMessage = error.message;
            } else {
                errorMessage += `: ${error.message}`;
            }
        }
        return {
            success: false,
            nodes: [],
            edges: [],
            rootId: null,
            error: errorMessage,
            rawDocument: undefined // AST might not be available if service failed
        };
    }
} 